import { DATA_BIND_REGEX_GLOBAL } from "constants/BindingsConstants";
import { isBoolean, get, set, isString } from "lodash";
import type {
  ConditionalOutput,
  FormConfigEvalObject,
  FormEvalOutput,
} from "reducers/evaluationReducers/formEvaluationReducer";
import type { FormConfigType, HiddenType } from "./BaseControl";
import type { Diff } from "deep-diff";
import { diff } from "deep-diff";
import { MongoDefaultActionConfig } from "constants/DatasourceEditorConstants";
import { klona } from "klona/full";
import type { FeatureFlags } from "ee/entities/FeatureFlag";
import _ from "lodash";
import { getType, Types } from "utils/TypeHelpers";
import { FIELD_REQUIRED_ERROR, createMessage } from "ee/constants/messages";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import { InputTypes } from "components/constants";

// This function checks if the form is dirty
// We needed this in the cases where datasources are created from APIs and the initial value
// already has url set. If user presses back button, we need to show the confirmation dialog
export const getIsFormDirty = (
  isFormDirty: boolean,
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  formData: any,
  isNewDatasource: boolean,
  isRestPlugin: boolean,
  currentEditingEnvId: string,
) => {
  const url = isRestPlugin
    ? get(
        formData,
        `datastoreStorages.${currentEditingEnvId}.datasourceConfiguration.url`,
        "",
      )
    : "";

  if (!isFormDirty && isNewDatasource && isRestPlugin && url.length === 0) {
    return true;
  }

  return isFormDirty;
};

// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const getTrimmedData = (formData: any) => {
  const dataType = getType(formData);
  const isArrayorObject = (type: ReturnType<typeof getType>) =>
    type === Types.ARRAY || type === Types.OBJECT;

  if (isArrayorObject(dataType)) {
    Object.keys(formData).map((key) => {
      const valueType = getType(formData[key]);

      if (isArrayorObject(valueType)) {
        getTrimmedData(formData[key]);
      } else if (valueType === Types.STRING) {
        _.set(formData, key, formData[key].trim());
      }
    });
  }

  return formData;
};

export const normalizeValues = (
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  formData: any,
  configDetails: Record<string, string>,
) => {
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const checked: Record<string, any> = {};
  const configProperties = Object.keys(configDetails);

  for (const configProperty of configProperties) {
    const controlType = configDetails[configProperty];

    if (controlType === "KEYVALUE_ARRAY") {
      const properties = configProperty.split("[*].");

      if (checked[properties[0]]) continue;

      checked[properties[0]] = 1;
      const values = _.get(formData, properties[0], []);
      const newValues: ({ [s: string]: unknown } | ArrayLike<unknown>)[] = [];

      values.forEach(
        (object: { [s: string]: unknown } | ArrayLike<unknown>) => {
          const isEmpty = Object.values(object).every((x) => x === "");

          if (!isEmpty) {
            newValues.push(object);
          }
        },
      );

      if (newValues.length) {
        formData = _.set(formData, properties[0], newValues);
      } else {
        formData = _.set(formData, properties[0], []);
      }
    }
  }

  return formData;
};

export const validate = (
  requiredFields: Record<string, FormConfigType>,
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  values: any,
  currentEnvId?: string,
) => {
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const errors = {} as any;

  Object.keys(requiredFields).forEach((fieldConfigProperty) => {
    // Do not check for required fields if the field is not part of the current environment
    if (
      !!currentEnvId &&
      currentEnvId.length > 0 &&
      !fieldConfigProperty.includes(currentEnvId)
    ) {
      return;
    }

    const fieldConfig = requiredFields[fieldConfigProperty];

    if (fieldConfig.controlType === "KEYVALUE_ARRAY") {
      const configProperty = (fieldConfig.configProperty as string).split(
        "[*].",
      );
      const arrayValues = _.get(values, configProperty[0], []);
      const keyValueArrayErrors: Record<string, string>[] = [];

      // TODO: Fix this the next time the file is edited
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      arrayValues.forEach((value: any, index: number) => {
        const objectKeys = Object.keys(value);
        const keyValueErrors: Record<string, string> = {};

        if (
          !value[objectKeys[0]] ||
          (isString(value[objectKeys[0]]) && !value[objectKeys[0]].trim())
        ) {
          keyValueErrors[objectKeys[0]] = createMessage(FIELD_REQUIRED_ERROR);
          keyValueArrayErrors[index] = keyValueErrors;
        }

        if (
          !value[objectKeys[1]] ||
          (isString(value[objectKeys[1]]) && !value[objectKeys[1]].trim())
        ) {
          keyValueErrors[objectKeys[1]] = createMessage(FIELD_REQUIRED_ERROR);
          keyValueArrayErrors[index] = keyValueErrors;
        }
      });

      if (keyValueArrayErrors.length) {
        _.set(errors, configProperty[0], keyValueArrayErrors);
      }
    } else {
      const value = _.get(values, fieldConfigProperty);

      if (_.isNil(value) || (isString(value) && _.isEmpty(value.trim()))) {
        _.set(errors, fieldConfigProperty, "This field is required");
      }
    }
  });

  return !_.isEmpty(errors);
};

export const evaluateCondtionWithType = (
  conditions: Array<boolean> | undefined,
  type: string | undefined,
) => {
  if (conditions) {
    let flag;

    //this is where each conditions gets evaluated
    if (conditions.length > 1) {
      if (type === "AND") {
        // TODO: Fix this the next time the file is edited
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        flag = conditions.reduce((acc: any, item: boolean) => {
          return acc && item;
        }, conditions[0]);
      } else if (type === "OR") {
        // TODO: Fix this the next time the file is edited
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        flag = conditions.reduce((acc: any, item: boolean) => {
          return acc || item;
        }, undefined);
      }
    } else {
      flag = conditions[0];
    }

    return flag;
  }
};

export const isHiddenConditionsEvaluation = (
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  values: any,
  hidden?: HiddenType,
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
): any => {
  if (!!hidden && !isBoolean(hidden)) {
    //if nested condtions are there recursively from bottom to top call this function on each condtion
    let conditionType, conditions;

    if ("conditionType" in hidden) {
      conditionType = hidden.conditionType;
    }

    if ("conditions" in hidden) {
      conditions = hidden.conditions;
    }

    if (Array.isArray(conditions)) {
      // TODO: Fix this the next time the file is edited
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      conditions = conditions.map((rule: any) => {
        return isHiddenConditionsEvaluation(values, rule);
      });
    } else {
      return caculateIsHidden(values, hidden);
    }

    return evaluateCondtionWithType(conditions, conditionType);
  }
};

export const caculateIsHidden = (
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  values: any,
  hiddenConfig?: HiddenType,
  featureFlags?: FeatureFlags,
  viewMode?: boolean,
) => {
  if (!!hiddenConfig && !isBoolean(hiddenConfig)) {
    let valueAtPath;
    let value, comparison;

    if ("path" in hiddenConfig) {
      valueAtPath = get(values, hiddenConfig.path);
    }

    if ("value" in hiddenConfig) {
      value = hiddenConfig.value;
    }

    if ("comparison" in hiddenConfig) {
      comparison = hiddenConfig.comparison;
    }

    let flagValue: keyof FeatureFlags = FEATURE_FLAG.TEST_FLAG;

    if ("flagValue" in hiddenConfig) {
      flagValue = hiddenConfig.flagValue;
    }

    switch (comparison) {
      case "EQUALS":
        return valueAtPath === value;
      case "NOT_EQUALS":
        return valueAtPath !== value;
      case "GREATER":
        return valueAtPath > value;
      case "LESSER":
        return valueAtPath < value;
      case "IN":
        return Array.isArray(value) && value.includes(valueAtPath);
      case "NOT_IN":
        return Array.isArray(value) && !value.includes(valueAtPath);
      case "FEATURE_FLAG":
        // FEATURE_FLAG comparision is used to hide previous configs,
        // and show new configs if feature flag is enabled, if disabled/ not present,
        // previous config would be shown as is
        return !!featureFlags && featureFlags[flagValue] === value;
      case "VIEW_MODE":
        // This can be used to decide which form controls to show in view mode or edit mode depending on the value.
        return viewMode === value;
      case "DEFINED_AND_NOT_EQUALS":
        return !!valueAtPath && valueAtPath !== value;
      default:
        return true;
    }
  }
};

export const isHidden = (
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  values: any,
  hiddenConfig?: HiddenType,
  featureFlags?: FeatureFlags,
  viewMode?: boolean,
) => {
  if (!!hiddenConfig && !isBoolean(hiddenConfig)) {
    if ("conditionType" in hiddenConfig) {
      //check if nested conditions exist
      return isHiddenConditionsEvaluation(values, hiddenConfig);
    } else {
      return caculateIsHidden(values, hiddenConfig, featureFlags, viewMode);
    }
  }

  return !!hiddenConfig;
};

export enum ViewTypes {
  JSON = "json",
  COMPONENT = "component",
}

export const alternateViewTypeInputConfig = () => {
  return {
    label: "",
    isValid: true,
    controlType: "QUERY_DYNAMIC_INPUT_TEXT",
    evaluationSubstitutionType: "TEMPLATE",
    inputType: "TEXT_WITH_BINDING",
    // showLineNumbers: true,
  };
};

// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const getViewType = (values: any, configProperty: string) => {
  if (
    configProperty.startsWith("actionConfiguration.formData") &&
    configProperty.endsWith(".data")
  ) {
    const pathForViewType = configProperty.replace(".data", ".viewType");

    return get(values, pathForViewType, ViewTypes.COMPONENT);
  } else {
    return ViewTypes.COMPONENT;
  }
};

export const switchViewType = (
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  values: any,
  configProperty: string,
  viewType: string,
  formName: string,
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  changeFormValue: (formName: string, path: string, value: any) => void,
) => {
  const newViewType =
    viewType === ViewTypes.JSON ? ViewTypes.COMPONENT : ViewTypes.JSON;
  const pathForJsonData = configProperty.replace(".data", ".jsonData");
  const pathForComponentData = configProperty.replace(
    ".data",
    ".componentData",
  );
  const componentData = get(values, pathForComponentData);
  const currentData = get(values, configProperty, "");
  const stringifiedCurrentData = JSON.stringify(currentData, null, "\t");

  if (newViewType === ViewTypes.JSON) {
    changeFormValue(formName, pathForComponentData, currentData);

    // when switching to JSON, we always want a form to json conversion of the data.
    changeFormValue(
      formName,
      configProperty,
      isString(currentData)
        ? currentData
        : stringifiedCurrentData.replace(/\\/g, ""),
    );
  } else {
    changeFormValue(formName, pathForJsonData, currentData);

    if (!!componentData) {
      changeFormValue(formName, configProperty, componentData);
    }
  }

  changeFormValue(
    formName,
    configProperty.replace(".data", ".viewType"),
    newViewType,
  );
};

// Function that extracts the initial value from the JSON configs
export const getConfigInitialValues = (
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  config: Record<string, any>[],
  multipleViewTypesSupported = false,
  // Used in case when we want to not have encrypted fields in the response since we need to compare
  // the initial values with the server response and server response does not send encrypted fields.
  // With this param we can remove false negatives during comparison.
  includeEncryptedFields = true,
) => {
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const configInitialValues: Record<string, any> = {};

  // We expect the JSON configs to be an array of objects
  if (!Array.isArray(config)) return configInitialValues;

  // Function to loop through the configs and extract the initial values
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const parseConfig = (section: any): any => {
    if (
      "initialValue" in section &&
      section.configProperty !== "runBehaviour"
    ) {
      if (section.controlType === "KEYVALUE_ARRAY") {
        section.initialValue.forEach(
          (initialValue: string | number, index: number) => {
            const configProperty = section.configProperty.replace("*", index);

            set(configInitialValues, configProperty, initialValue);
          },
        );
      } else {
        if (
          !includeEncryptedFields &&
          (section.encrypted || section.dataType === InputTypes.PASSWORD)
        ) {
          return;
        }

        set(configInitialValues, section.configProperty, section.initialValue);
      }
    } else if (section.controlType === "WHERE_CLAUSE") {
      let logicalTypes = [];

      if ("logicalTypes" in section && section.logicalTypes.length > 0) {
        logicalTypes = section.logicalTypes;
      } else {
        logicalTypes = [
          {
            label: "OR",
            value: "OR",
          },
          {
            label: "AND",
            value: "AND",
          },
        ];
      }

      set(
        configInitialValues,
        `${section.configProperty}.condition`,
        logicalTypes[0].value,
      );

      if (
        multipleViewTypesSupported &&
        section.configProperty.includes(".data")
      ) {
        set(
          configInitialValues,
          section.configProperty.replace(".data", ".viewType"),
          "component",
        );
        set(
          configInitialValues,
          section.configProperty.replace(".data", ".componentData.condition"),
          logicalTypes[0].value,
        );
      }
    }

    if ("children" in section) {
      // TODO: Fix this the next time the file is edited
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      section.children.forEach((childSection: any) => {
        parseConfig(childSection);
      });
    } else if ("schema" in section) {
      // TODO: Fix this the next time the file is edited
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      section.schema.forEach((childSection: any) => {
        parseConfig(childSection);
      });
    } else if (
      "configProperty" in section &&
      multipleViewTypesSupported &&
      section.configProperty.includes(".data")
    ) {
      set(
        configInitialValues,
        section.configProperty.replace(".data", ".viewType"),
        "component",
      );

      if (section.configProperty in configInitialValues) {
        set(
          configInitialValues,
          section.configProperty.replace(".data", ".componentData"),
          configInitialValues[section.configProperty],
        );
      }
    }
  };

  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  config.forEach((section: any) => {
    parseConfig(section);
  });

  return configInitialValues;
};

export const actionPathFromName = (
  actionName: string,
  name: string,
): string => {
  const ActionConfigStarts = "actionConfiguration.";
  let path = name;

  if (path.startsWith(ActionConfigStarts)) {
    path = "config." + path.slice(ActionConfigStarts.length);
  }

  return `${actionName}.${path}`;
};

export enum PaginationSubComponent {
  Limit = "limit",
  Offset = "offset",
  Cursor = "cursor",
}

export enum SortingSubComponent {
  Column = "column",
  Order = "order",
}

export enum WhereClauseSubComponent {
  Condition = "condition",
  Children = "children",
  Key = "key",
  Value = "value",
}

export const allowedControlTypes = ["DROP_DOWN", "QUERY_DYNAMIC_INPUT_TEXT"];

const extractExpressionObject = (
  config: string,
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  path: any,
  parentPath: string,
): FormConfigEvalObject => {
  const bindingPaths: FormConfigEvalObject = {};
  const expressions = config.match(DATA_BIND_REGEX_GLOBAL);

  if (Array.isArray(expressions) && expressions.length > 0) {
    const completePath = parentPath.length > 0 ? `${parentPath}.${path}` : path;

    expressions.forEach((exp) => {
      bindingPaths[completePath] = {
        expression: exp,
        output: "",
      };
    });
  }

  return bindingPaths;
};

export const extractEvalConfigFromFormConfig = (
  formConfig: FormConfigType,
  paths: string[],
  parentPath = "",
  bindingsFound: FormConfigEvalObject = {},
) => {
  paths.forEach((path: string) => {
    if (!(path in formConfig)) return;

    const config = get(formConfig, path, "");

    if (typeof config === "string") {
      bindingsFound = {
        ...bindingsFound,
        ...extractExpressionObject(config, path, parentPath),
      };
    } else if (typeof config === "object") {
      bindingsFound = {
        ...bindingsFound,
        ...extractEvalConfigFromFormConfig(
          config,
          Object.keys(config),
          parentPath.length > 0 ? `${parentPath}.${path}` : path,
          bindingsFound,
        ),
      };
    }
  });

  return bindingsFound;
};

// Extract the output of conditionals attached to the form from the state
export const extractConditionalOutput = (
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  section: any,
  formEvaluationState: FormEvalOutput,
): ConditionalOutput => {
  let conditionalOutput: ConditionalOutput = {};

  if (
    section.hasOwnProperty("propertyName") &&
    formEvaluationState.hasOwnProperty(section.propertyName)
  ) {
    conditionalOutput = formEvaluationState[section.propertyName];
  } else if (
    section.hasOwnProperty("configProperty") &&
    formEvaluationState.hasOwnProperty(section.configProperty)
  ) {
    conditionalOutput = formEvaluationState[section.configProperty];
  } else if (
    section.hasOwnProperty("identifier") &&
    !!section.identifier &&
    formEvaluationState.hasOwnProperty(section.identifier)
  ) {
    conditionalOutput = formEvaluationState[section.identifier];
  }

  return conditionalOutput;
};

// Function to check if the section config is allowed to render (Only for UQI forms)
export const checkIfSectionCanRender = (
  conditionalOutput: ConditionalOutput,
) => {
  // By default, allow the section to render. This is to allow for the case where no conditional is provided.
  // The evaluation state disallows the section to render if the condition is not met. (Checkout formEval.ts)
  let allowToRender = true;

  if (
    conditionalOutput.hasOwnProperty("visible") &&
    typeof conditionalOutput.visible === "boolean"
  ) {
    allowToRender = conditionalOutput.visible;
  }

  if (
    conditionalOutput.hasOwnProperty("evaluateFormConfig") &&
    !!conditionalOutput.evaluateFormConfig &&
    conditionalOutput.evaluateFormConfig.hasOwnProperty(
      "updateEvaluatedConfig",
    ) &&
    typeof conditionalOutput.evaluateFormConfig.updateEvaluatedConfig ===
      "boolean"
  ) {
    allowToRender = conditionalOutput.evaluateFormConfig.updateEvaluatedConfig;
  }

  return allowToRender;
};

// Function to check if the section config is enabled (Only for UQI forms)
export const checkIfSectionIsEnabled = (
  conditionalOutput: ConditionalOutput,
) => {
  // By default, the section is enabled. This is to allow for the case where no conditional is provided.
  // The evaluation state disables the section if the condition is not met. (Checkout formEval.ts)
  let enabled = true;

  if (
    conditionalOutput.hasOwnProperty("enabled") &&
    typeof conditionalOutput.enabled === "boolean"
  ) {
    enabled = conditionalOutput.enabled;
  }

  return enabled;
};

// Function to modify the section config based on the output of evaluations
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const modifySectionConfig = (section: any, enabled: boolean): any => {
  if (!enabled) {
    section.disabled = true;
  } else {
    section.disabled = false;
  }

  return section;
};

export const updateEvaluatedSectionConfig = (
  // TODO: Fix this the next time the file is edited
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  section: any,
  conditionalOutput: ConditionalOutput,
  enabled = true,
) => {
  // we deep clone the section coming from the editorConfig to prevent any mutations of
  // the editorConfig in the redux state.
  // just spreading the object does a shallow clone(top level cloning), so we use the klona package to deep clone
  // klona is faster than deepClone from lodash.

  // leaving the commented code as a reminder of the above observation.
  // const updatedSection = { ...section };

  const updatedSection = klona(section);

  let evaluatedConfig: FormConfigEvalObject = {};

  if (
    conditionalOutput.hasOwnProperty("evaluateFormConfig") &&
    !!conditionalOutput.evaluateFormConfig &&
    conditionalOutput.evaluateFormConfig.hasOwnProperty(
      "updateEvaluatedConfig",
    ) &&
    typeof conditionalOutput.evaluateFormConfig.updateEvaluatedConfig ===
      "boolean" &&
    conditionalOutput.evaluateFormConfig.updateEvaluatedConfig
  ) {
    evaluatedConfig =
      conditionalOutput.evaluateFormConfig.evaluateFormConfigObject;

    const paths = Object.keys(evaluatedConfig);

    paths.forEach((path: string) => {
      set(updatedSection, path, evaluatedConfig[path].output);
    });
  }

  return modifySectionConfig(updatedSection, enabled);
};

export function fixActionPayloadForMongoQuery(
  action?: unknown,
): unknown | undefined {
  if (!action) return action;

  /* eslint-disable */
  //@ts-nocheck
  try {
    let actionObjectDiff: undefined | Diff<any, any>[] = diff(
      action,
      MongoDefaultActionConfig,
    );
    if (actionObjectDiff) {
      actionObjectDiff = actionObjectDiff.filter((diff) => diff.kind === "N");
      for (let i = 0; i < actionObjectDiff.length; i++) {
        let path = "";
        let value = "";
        //kind = N indicates a newly added property/element
        //This property is present in initialValues but not in action object
        if (
          actionObjectDiff &&
          actionObjectDiff[i].hasOwnProperty("kind") &&
          actionObjectDiff[i].path &&
          Array.isArray(actionObjectDiff[i].path) &&
          actionObjectDiff[i]?.path?.length &&
          actionObjectDiff[i]?.kind === "N"
        ) {
          // @ts-expect-error: Types are not available
          if (typeof actionObjectDiff[i]?.path[0] === "string") {
            // @ts-expect-error: Types are not available
            path = actionObjectDiff[i]?.path?.join(".");
          }
          // @ts-expect-error: Types are not available
          value = actionObjectDiff[i]?.rhs;
          set(action, path, value);
        }
      }
    }
    return action;
    //@ts-check
  } catch (error) {
    console.error("Error adding default paths in Mongo query");
    return action;
  }
}

// Function to check if the config has KEYVALUE_ARRAY controlType with more than 1 dependent children
export function isKVArray(children: Array<any>) {
  if (!Array.isArray(children) || children.length < 2) return false;
  return (
    children[0].controlType && children[0].controlType === "KEYVALUE_ARRAY"
  );
}

export const formatFileSize = (sizeInBytes: number) => {
  const FILE_SIZE = {
    KB: 1024,
    MB: 1024 * 1024,
    GB: 1024 * 1024 * 1024,
  };

  if (sizeInBytes < FILE_SIZE.KB) {
    return `${sizeInBytes} B`;
  } else if (sizeInBytes < FILE_SIZE.MB) {
    return `${Math.round(sizeInBytes / FILE_SIZE.KB)} KB`;
  } else if (sizeInBytes < FILE_SIZE.GB) {
    return `${Math.round(sizeInBytes / FILE_SIZE.MB)} MB`;
  }

  return `${Math.round(sizeInBytes / FILE_SIZE.GB)} GB`;
};
